App.require(["math"], function (_Math) {
    function Vector4(x, y, z, w) {
        this.x = x || 0;
        this.y = y || 0;
        this.z = z || 0;
        this.w = (w !== undefined) ? w : 1;
    }
    Vector4.prototype = {
        constructor: Vector4,
    
        set: function (x, y, z, w) {
            this.x = x;
            this.y = y;
            this.z = z;
            this.w = w;
    
            return this;
        },
    
        clone: function () {
            return new this.constructor(this.x, this.y, this.z, this.w);
        },
    
        copy: function () {
            this.x = v.x;
            this.y = v.y;
            this.z = v.z;
            this.w = ( v.w !== undefined ) ? v.w : 1;
    
            return this;
        },
    
        add: function (v) {
            this.x += v.x;
            this.y += v.y;
            this.z += v.z;
            this.w += v.w;
    
            return this;
        },
    
        addScalar: function (s) {
            this.x += s;
            this.y += s;
            this.z += s;
            this.w += s;
    
            return this;
        },
    
        addScaledVector: function (v, s) {
            this.x += v.x * s;
            this.y += v.y * s;
            this.z += v.z * s;
            this.w += v.w * s;
    
            return this;
        },
    
        sub: function (v) {
            this.x -= v.x;
            this.y -= v.y;
            this.z -= v.z;
            this.w -= v.w;
    
            return this;
        },
    
        subScalar: function (s) {
            this.x -= s;
            this.y -= s;
            this.z -= s;
            this.w -= s;
    
            return this;
        },
    
        multiply: function (v) {
            this.x *= v.x;
            this.y *= v.y;
            this.z *= v.z;
            this.w *= v.w;
            return this;
        },
    
        multiplyScalar: function (scalar) {
            this.x *= scalar;
            this.y *= scalar;
            this.z *= scalar;
            this.w *= scalar;
    
            return this;
        },
    
        applyMatrix4: function (m) {
            var x = this.x, y = this.y, z = this.z, w = this.w;
            var e = m.elements;
    
            this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w;
            this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w;
            this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w;
            this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w;
    
            return this;
        },
    
        divide: function (v) {
            this.x /= v.x;
            this.y /= v.y;
            this.z /= v.z;
            this.w /= v.w;
    
            return this;
        },
    
        divideScalar: function (scalar) {
            return this.multiplyScalar(1 / scalar);
        },
    
        setAxisAngleFromQuaternion: function (q) {
            this.w = 2 * Math.acos( q.w );
            var s = Math.sqrt( 1 - q.w * q.w );
            if ( s < 0.0001 ) {
                this.x = 1;
                this.y = 0;
                this.z = 0;
            } else {
                this.x = q.x / s;
                this.y = q.y / s;
                this.z = q.z / s;
            }
            return this;
        },
    
        setAxisAngleFromRotationMatrix: function (m) {
            var angle, x, y, z,		
                epsilon = 0.01,		
                epsilon2 = 0.1,		
    
                te = m.elements,
    
                m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
                m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
                m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ];
    
            if ( ( Math.abs( m12 - m21 ) < epsilon ) &&
                 ( Math.abs( m13 - m31 ) < epsilon ) &&
                 ( Math.abs( m23 - m32 ) < epsilon ) ) {
    
                if ( ( Math.abs( m12 + m21 ) < epsilon2 ) &&
                     ( Math.abs( m13 + m31 ) < epsilon2 ) &&
                     ( Math.abs( m23 + m32 ) < epsilon2 ) &&
                     ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) {
    
                    this.set( 1, 0, 0, 0 );
    
                    return this; 
    
                }
    
                angle = Math.PI;
    
                var xx = ( m11 + 1 ) / 2;
                var yy = ( m22 + 1 ) / 2;
                var zz = ( m33 + 1 ) / 2;
                var xy = ( m12 + m21 ) / 4;
                var xz = ( m13 + m31 ) / 4;
                var yz = ( m23 + m32 ) / 4;
    
                if ( ( xx > yy ) && ( xx > zz ) ) {
    
                    if ( xx < epsilon ) {
    
                        x = 0;
                        y = 0.707106781;
                        z = 0.707106781;
    
                    } else {
    
                        x = Math.sqrt( xx );
                        y = xy / x;
                        z = xz / x;
    
                    }
    
                } else if ( yy > zz ) {
    
                    if ( yy < epsilon ) {
    
                        x = 0.707106781;
                        y = 0;
                        z = 0.707106781;
    
                    } else {
    
                        y = Math.sqrt( yy );
                        x = xy / y;
                        z = yz / y;
    
                    }
    
                } else {
    
                    if ( zz < epsilon ) {
    
                        x = 0.707106781;
                        y = 0.707106781;
                        z = 0;
    
                    } else {
    
                        z = Math.sqrt( zz );
                        x = xz / z;
                        y = yz / z;
    
                    }
    
                }
    
                this.set( x, y, z, angle );
    
                return this;
            }
    
            var s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) +
                               ( m13 - m31 ) * ( m13 - m31 ) +
                               ( m21 - m12 ) * ( m21 - m12 ) ); 
    
            if ( Math.abs( s ) < 0.001 ) s = 1;
    
            this.x = ( m32 - m23 ) / s;
            this.y = ( m13 - m31 ) / s;
            this.z = ( m21 - m12 ) / s;
            this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 );
    
            return this;
        },
    
        min: function (v) {
            this.x = Math.min( this.x, v.x );
            this.y = Math.min( this.y, v.y );
            this.z = Math.min( this.z, v.z );
            this.w = Math.min( this.w, v.w );
    
            return this;
        },
    
        max: function (v) {
            this.x = Math.max( this.x, v.x );
            this.y = Math.max( this.y, v.y );
            this.z = Math.max( this.z, v.z );
            this.w = Math.max( this.w, v.w );
    
            return this;
        },
    
        clamp: function (min, max) {
            this.x = Math.max( min.x, Math.min( max.x, this.x ) );
            this.y = Math.max( min.y, Math.min( max.y, this.y ) );
            this.z = Math.max( min.z, Math.min( max.z, this.z ) );
            this.w = Math.max( min.w, Math.min( max.w, this.w ) );
    
            return this;
        },
    
        clampScalar: function (minVal, maxVal) {
            this.x = Math.max( minVal, Math.min( maxVal, this.x ) );
            this.y = Math.max( minVal, Math.min( maxVal, this.y ) );
            this.z = Math.max( minVal, Math.min( maxVal, this.z ) );
            this.w = Math.max( minVal, Math.min( maxVal, this.w ) );
        },
    
        clampLength: function (min, max) {
            var length = this.length();
            return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );
        },
    
        negate: function () {
            this.x = - this.x;
            this.y = - this.y;
            this.z = - this.z;
            this.w = - this.w;
    
            return this;
        },
    
        equals: function (v) {
            return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) );
        },
    
        toArray: function (array, offset) {
            if ( array === undefined ) array = [];
            if ( offset === undefined ) offset = 0;
    
            array[ offset ] = this.x;
            array[ offset + 1 ] = this.y;
            array[ offset + 2 ] = this.z;
            array[ offset + 3 ] = this.w;
    
            return array;
        },
        normalize: function () {
            return Vector4.normalize(this.x, this.y, this.z, this.w, this);
        },
    };

    Vector4.normalize = function (x, y, z, w, v) {
        v = v || new Vector4();
        var length = Math.sqrt(x * x + y * y + z * z + w * w);
        v.x = x / length;
        v.y = y / length;
        v.z = z / length;
        v.w = w / length;
        return v;
    };
    
    Vector4.add = function (a, b, c) {
        c = c || new Vector4();
        c.x = a.x + b.x;
        c.y = a.y + b.y;
        c.z = c.z + c.z;
        c.w = c.w + c.w;
        return c;
    };
    
    Vector4.sub = function (a, b, c) {
        c = c || new Vector4();
        c.x = a.x - b.x;
        c.y = a.y - b.y;
        c.z = a.z - b.z;
        c.w = a.w - b.w;
        return c;
    };
    
    Vector4.multiply = function (a, b, c) {
        c = c || new Vector4();
        c.x = a.x * b.x;
        c.y = a.y * b.y;
        c.z = a.z * b.z;
        c.w = a.w * b.w;
        return c;
    };
    
    Vector4.divide = function (a, b, c) {
        c = c || new Vector4();
        c.x = a.x / b.x;
        c.y = a.y / b.y;
        c.z = a.z / b.z;
        c.w = a.w / b.w;
        return c;
    };
    
    Vector4.lerp = function (a, b, t, c) {
        var c = c || new Vector4();
        c.x = _Math.lerp(a.x, b.x, t);
        c.y = _Math.lerp(a.y, b.y, t);
        c.z = _Math.lerp(a.z, b.z, t);
        c.w = _Math.lerp(a.w, b.w, t);
        return c;
    };
    
    Vector4.smoothstep = function (a, b, t, c) {
        var c = c || new Vector4();
        c.x = _Math.smoothstep(a.x, b.x, t);
        c.y = _Math.smoothstep(a.y, b.y, t);
        c.z = _Math.smoothstep(a.z, b.z, t);
        c.w = _Math.smoothstep(a.w, b.w, t);
        return c;
    };
    
    Vector4.fromArray = function (array, offset, c) {
        c = c || new Vector4();
        offset = offset || 0;
        c.x = array[offset];
        c.y = array[offset + 1];
        c.z = array[offset + 2];
        c.w = array[offset + 3];
        return c;
    };

    Vector4.fromMatrixColumn = function (m, index, c) {
        c = c || new Vector4();
        return Vector4.fromArray(m.elements, index * 4, c);
    };

    App.define("vector4", {
        Vector4: Vector4
    })
})